package com.hemerasolutions.v2trainer.dao;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.mutable.MutableInt;
import org.springframework.stereotype.Repository;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.StoredCredential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.DataStore;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.Channel;
import com.google.api.services.youtube.model.ChannelListResponse;
import com.google.api.services.youtube.model.PlaylistItem;
import com.google.api.services.youtube.model.PlaylistItemListResponse;
import com.google.api.services.youtube.model.VideoListResponse;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.api.services.youtube.model.VideoStatus;
import com.google.common.collect.Lists;
import com.hemerasolutions.v2trainer.controller.youTube.Auth;
import com.hemerasolutions.v2trainer.dao.interfaces.RemoteVideoDaoInterface;
import com.hemerasolutions.v2trainer.model.multimedia.RemoteVideo;
import com.hemerasolutions.v2trainer.model.multimedia.RemoteVideo_;

@Repository(value = "remoteVideoDao")
public class RemoteVideoDao extends BaseDao<RemoteVideo> implements RemoteVideoDaoInterface{
	private static final long serialVersionUID = 1L;

	private transient LocalServerReceiver localServerReceiver;

    /**
     * This is the directory that will be used under the user's home directory where OAuth tokens will be stored.
     */
    private static String CREDENTIALS_DIRECTORY = ".oauth-credentials";

    /**
     * Define a global variable that specifies the MIME type of the video
     * being uploaded.
     */
    private static final String VIDEO_FILE_FORMAT = "video/*";


    @Override
	public void disconnect() throws ExceptionDao{
    	try {
			FileUtils.deleteDirectory(new File(System.getProperty("user.home") + "/" + CREDENTIALS_DIRECTORY));
			localServerReceiver.stop();
			localServerReceiver = null;
    	} catch (IOException e) {
			throw new ExceptionDao(e.getMessage());
		}
    }
    
    
    /**
     * Upload the user-selected video to the user's YouTube channel. The code
     * looks for the video in the application's project folder and uses OAuth
     * 2.0 to authorize the API request.
     *
     * @param args command line args (not used).
     */
    private RemoteVideo upload(RemoteVideo v2RmoteVideo, final MutableInt progress) throws ExceptionDao {

        try {

        	HttpTransport httpTransport = new NetHttpTransport();
        	JsonFactory jsonFactory		= new JacksonFactory();
        	
        	
            // Authorize the request.
            Credential credential = this.authorize(httpTransport,jsonFactory,"general");

            // This object is used to make YouTube Data API requests.
            YouTube youtube = new YouTube.Builder(httpTransport, jsonFactory, credential).setApplicationName(
                    "hrm-v2trainer").build();

            // Add extra information to the video before uploading.
            com.google.api.services.youtube.model.Video videoObjectDefiningMetadata = new com.google.api.services.youtube.model.Video();

            // Set the video to be publicly visible. This is the default
            // setting. Other supporting settings are "unlisted" and "private."
            VideoStatus status = new VideoStatus();
            status.setPrivacyStatus("unlisted");
            videoObjectDefiningMetadata.setStatus(status);

            // Most of the video's metadata is set on the VideoSnippet object.
            VideoSnippet snippet = new VideoSnippet();

            snippet.setTitle("V2 Video");
            
            // Set the keyword tags that you want to associate with the video.
            List<String> tags = new ArrayList<String>();
            tags.add(v2RmoteVideo.getVideoHascode());
            tags.add("v2trainervideo");
            snippet.setTags(tags);

            // Add the completed snippet object to the video resource.
            videoObjectDefiningMetadata.setSnippet(snippet);

            InputStreamContent mediaContent =
            	    new InputStreamContent(VIDEO_FILE_FORMAT,
            	        new BufferedInputStream(v2RmoteVideo.getUploadedFile().getInputstream()));
            	mediaContent.setLength(v2RmoteVideo.getUploadedFile().getSize());

            // Insert the video. The command sends three arguments. The first
            // specifies which information the API request is setting and which
            // information the API response should return. The second argument
            // is the video resource that contains metadata about the new video.
            // The third argument is the actual video content.
            YouTube.Videos.Insert videoInsert = youtube.videos()
                    .insert("snippet,statistics,status", videoObjectDefiningMetadata, mediaContent);

            
            
            // Set the upload type and add an event listener.
            MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
            uploader.setChunkSize(MediaHttpUploader.MINIMUM_CHUNK_SIZE);
            
            // Indicate whether direct media upload is enabled. A value of
            // "True" indicates that direct media upload is enabled and that
            // the entire media content will be uploaded in a single request.
            // A value of "False," which is the default, indicates that the
            // request will use the resumable media upload protocol, which
            // supports the ability to resume an upload operation after a
            // network interruption or other transmission failure, saving
            // time and bandwidth in the event of network failures.
            uploader.setDirectUploadEnabled(false);
            
            MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() {
                public void progressChanged(MediaHttpUploader uploader) throws IOException {
                    switch (uploader.getUploadState()) {
                        case INITIATION_STARTED:
                            break;
                        case INITIATION_COMPLETE:
                            break;
                        case MEDIA_IN_PROGRESS:
                            progress.setValue((int)(uploader.getProgress() * 100));
                            break;
                        case MEDIA_COMPLETE:
                        	progress.setValue((int)100);
                        	break;
                        case NOT_STARTED:
                            progress.setValue((int)0);
                            throw new ExceptionDao("UploadNotStarted");
                    }
                }
            };
            
            uploader.setProgressListener(progressListener);
            

            // Call the API and upload the video.
            com.google.api.services.youtube.model.Video returnedVideo = videoInsert.execute();

            v2RmoteVideo.setStorageId(returnedVideo.getId());	
            
            return v2RmoteVideo;
            
        } catch (GoogleJsonResponseException e) {
        	e.printStackTrace();
        	throw new ExceptionDao(e.getMessage());
        } catch (IOException e) {
        	e.printStackTrace();
        	throw new ExceptionDao(e.getMessage());
        } catch (Throwable t) {
        	t.printStackTrace();
        	throw new ExceptionDao(t.getMessage());
        }
    }
    
	
	@Override
	public RemoteVideo persistInRemoteStorage(RemoteVideo remoteVideo, MutableInt progress) throws ExceptionDao{
		
		//Verificar se o video ja esta na base para outro Id
    	if(remoteVideo == null || remoteVideo.getUploadedFile() == null){
    		throw new ExceptionDao(this.localizedText("CannotUploadEmptyVideo"));
    	}
		
		
		RemoteVideo remoteVideoAux = this.findByRemoteVideoHashId(remoteVideo.getVideoHascode());

		if(remoteVideoAux != null){
			progress.setValue(100);
			return remoteVideoAux;
		}else{

			String existentId = checkAlreadyInYouTube(remoteVideo.getVideoHascode());

			if(existentId != null){
				progress.setValue(100);
				RemoteVideo rm = new RemoteVideo();
				rm.setStorageId(existentId);
				rm.setUuid(rm.getUuid());
				rm.setVideoHascode(remoteVideo.getVideoHascode());
				
				return rm ;
			}else{
				return this.upload(remoteVideo, progress);
			}
		}
	}
	
	
	@Override
	public RemoteVideo persistInRemoteStorage(RemoteVideo remoteVideo) throws ExceptionDao{
		MutableInt progress = new MutableInt(0);
		return this.persistInRemoteStorage(remoteVideo, progress);
	}

	
	@Override
	public List<RemoteVideo> findByStorageId(String storageId) {
		CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();

		CriteriaQuery<RemoteVideo> cq  = cb.createQuery(RemoteVideo.class);
		
		Root<RemoteVideo> video = cq.from(RemoteVideo.class);

		Predicate pred = cb.equal(cb.upper(video.get(RemoteVideo_.storageId)),storageId);

	    cq.where(pred);
	 
	    if( 	super.getEntityManager().createQuery(cq).getResultList() == null ||
	    		super.getEntityManager().createQuery(cq).getResultList().isEmpty()	){
	    	return null;
	    }else{
		    return super.getEntityManager().createQuery(cq).getResultList();
	    }
	}

    private Credential authorize(HttpTransport httpTransport, JsonFactory jsonFactory, String credentialDatastore) throws IOException {

        // Load client secrets.
        Reader clientSecretReader = new InputStreamReader(Auth.class.getResourceAsStream("/client_secrets.json"));
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(jsonFactory, clientSecretReader);

	    // This OAuth 2.0 access scope allows for read-only access to the
	    // authenticated user's account, but not other types of account access.
	    List<String> scopes = Lists.newArrayList(	"https://www.googleapis.com/auth/youtube.readonly",
	    											"https://www.googleapis.com/auth/youtube.upload",
	    											"https://www.googleapis.com/auth/youtube");

        // This creates the credentials datastore at ~/.oauth-credentials/${credentialDatastore}
        FileDataStoreFactory fileDataStoreFactory = new FileDataStoreFactory(new File(System.getProperty("user.home") + "/" + CREDENTIALS_DIRECTORY));
        DataStore<StoredCredential> datastore = fileDataStoreFactory.getDataStore(credentialDatastore);

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                httpTransport, jsonFactory, clientSecrets, scopes)
        		.setCredentialDataStore(datastore)
        		.setAccessType("offline")
        		.setApprovalPrompt("force")
        		.build();

        if(localServerReceiver == null){
        	localServerReceiver = new LocalServerReceiver.Builder().setPort(9001).build();
        }else{
        	localServerReceiver.stop();
        	localServerReceiver = new LocalServerReceiver.Builder().setPort(9001).build();
        }
        
        
        
        // Authorize.
        Credential credential = new AuthorizationCodeInstalledApp(flow, localServerReceiver).authorize("user");
        credential.refreshToken();

        return credential;
    }	

    
    @Override
	public List<RemoteVideo> listVideosInYouTubeAndDb() throws ExceptionDao{
    	List<RemoteVideo> remoteVideos = new ArrayList<RemoteVideo>();
    	
    	List<com.google.api.services.youtube.model.Video> items = new ArrayList<com.google.api.services.youtube.model.Video>();
    	
    	items = this.getYouTubeVideos();
    	
    	for(com.google.api.services.youtube.model.Video item:items){
    		remoteVideos.addAll(this.findByStorageId(item.getId()));
    	}
    	

    	return remoteVideos;
    }
    
    
    
	@Override
	public void deleteYouTubeVideo(RemoteVideo remoteVideo) throws ExceptionDao{
        try {

        	HttpTransport httpTransport = new NetHttpTransport();
        	JsonFactory jsonFactory		= new JacksonFactory();
        	
        	
            // Authorize the request.
            Credential credential = this.authorize(httpTransport,jsonFactory,"general");

        	
            // This object is used to make YouTube Data API requests.
            YouTube youtube = new YouTube.Builder(httpTransport, jsonFactory, credential)
                    .setApplicationName("youtube-cmdline-updatevideo-sample").build();

            // Delete Video on youtube
            YouTube.Videos.Delete updateVideosRequest = youtube.videos().delete(remoteVideo.getStorageId());
            updateVideosRequest.execute();
            		

        } catch (GoogleJsonResponseException e) {
        	throw new ExceptionDao(e.getDetails().getMessage());
        	
        } catch (IOException e) {
        	throw new ExceptionDao(e.getMessage());

        } catch (Throwable t) {
        	throw new ExceptionDao(t.getMessage());
        }    	
    	
    }

    
	@Override
	public void deleteYouTubeVideos(List<RemoteVideo> remoteVideos) throws ExceptionDao{
    	for(RemoteVideo rm:remoteVideos){
    		try{
    			this.deleteYouTubeVideo(rm);
    		}catch(ExceptionDao ex){
    			// called only by job
    			System.out.println("Can´t delete video: " + rm);    		
    			System.out.println("Reason: " + ex.getMessage());    		
    			throw ex;
    		}
    	}
    	
    }
    
    
	@Override
	public List<com.google.api.services.youtube.model.Video> listVideosInYouTube() throws ExceptionDao{
    	List<com.google.api.services.youtube.model.Video> items = new ArrayList<com.google.api.services.youtube.model.Video>();
    	
    	try{
        	items.addAll(this.getYouTubeVideos());
        	return items;
    	}catch(ExceptionDao ex){
    		throw ex;
    	}
    }
    

    private List<com.google.api.services.youtube.model.Video> getYouTubeVideos() throws ExceptionDao {

    	List<com.google.api.services.youtube.model.Video> videosList = new ArrayList<com.google.api.services.youtube.model.Video>();
    	
    	try {

        	HttpTransport httpTransport = new NetHttpTransport();
        	JsonFactory jsonFactory		= new JacksonFactory();
        	
    		
    		// Authorize the request.
	        Credential credential = this.authorize(httpTransport,jsonFactory,"general");

            // This object is used to make YouTube Data API requests.
	        YouTube youtube = new YouTube.Builder(httpTransport, jsonFactory, credential).setApplicationName(
                    "hmr-v2trainer").build();

            
            
            // Call the API's channels.list method to retrieve the
            // resource that represents the authenticated user's channel.
            // In the API response, only include channel information needed for
            // this use case. The channel's contentDetails part contains
            // playlist IDs relevant to the channel, including the ID for the
            // list that contains videos uploaded to the channel.
           
            YouTube.Channels.List channelRequest = youtube.channels().list("contentDetails");
            channelRequest.setMine(true);
            channelRequest.setFields("items/contentDetails,nextPageToken,pageInfo");
            ChannelListResponse channelResult = channelRequest.execute();

            List<Channel> channelsList = channelResult.getItems();

            if (channelsList != null) {
                // The user's default channel is the first item in the list.
                // Extract the playlist ID for the channel's videos from the
                // API response.
                String uploadPlaylistId =
                        channelsList.get(0).getContentDetails().getRelatedPlaylists().getUploads();


                // Retrieve the playlist of the channel's uploaded videos.
                YouTube.PlaylistItems.List playlistItemRequest =
                        youtube.playlistItems().list("id,contentDetails");
                playlistItemRequest.setPlaylistId(uploadPlaylistId);

//                // Only retrieve data used in this application, thereby making
//
//                // the application more efficient. See:
//                // https://developers.google.com/youtube/v3/getting-started#partial
                playlistItemRequest.setFields(
                        "items(contentDetails/videoId),nextPageToken,pageInfo");

                String nextToken = "";

                
                // Call the API one or more times to retrieve all items in the
                // list. As long as the API response returns a nextPageToken,
                // there are still more items to retrieve.
                do {
                    playlistItemRequest.setPageToken(nextToken);
                    PlaylistItemListResponse playlistItemResult = playlistItemRequest.execute();
 

                    
                    for(PlaylistItem pi:playlistItemResult.getItems()){

                        YouTube.Videos.List listVideosRequest = youtube.videos().list("id,contentDetails,snippet,status");                    	
                        listVideosRequest.setId(pi.getContentDetails().getVideoId());
                        
                        listVideosRequest.setFields("items(id,snippet/title,snippet/description,snippet/publishedAt,snippet/tags,status)");
                        
                        VideoListResponse response = listVideosRequest.execute();
                        
                        videosList.addAll(response.getItems());
                    }

                    nextToken = playlistItemResult.getNextPageToken();
                } while (nextToken != null);
           	}
            
            return videosList;

        } catch (GoogleJsonResponseException e) {
            throw new ExceptionDao(e.getDetails().getMessage());
        } catch (Throwable t) {
            throw new ExceptionDao(t.getMessage());
        }    
        
	}    
    
    
	@Override
	public String checkAlreadyInYouTube( String videoHashCode ){

    	for(com.google.api.services.youtube.model.Video item:this.getYouTubeVideos()){
    		if(item.getSnippet().getTags().contains(videoHashCode)){
    			return item.getId();
    		}
    	}
		
    	return null;
    	
    }
    
	
	@Override
	public List<RemoteVideo> persistInRemoteStorage(List<RemoteVideo> remoteVideos, MutableInt progress) throws ExceptionDao  {
		
		List<RemoteVideo> persisteds = new ArrayList<RemoteVideo>();
		
		try{
			for(int x = 0; x < remoteVideos.size();x++){
				persisteds.add(this.persistInRemoteStorage(remoteVideos.get(x), progress));
			}	
			return persisteds;
		}catch (ExceptionDao ex) {
			throw ex;
		}
	}
    
	@Override
	public RemoteVideo findByRemoteVideoHashId(String videoHashCode){

		CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();

		CriteriaQuery<RemoteVideo> cq  = cb.createQuery(RemoteVideo.class);
		
		Root<RemoteVideo> video = cq.from(RemoteVideo.class);

		Predicate pred = cb.equal(video.get(RemoteVideo_.videoHascode),videoHashCode);

	    cq.where(pred);
	 
	    if( 	super.getEntityManager().createQuery(cq).getResultList() == null ||
	    		super.getEntityManager().createQuery(cq).getResultList().isEmpty()	){
	    	return null;
	    }else{
		    return super.getEntityManager().createQuery(cq).getResultList().get(0);
	    }
	}


	@Override
	public RemoteVideo getByUuid(long uuid) {
		CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();

		CriteriaQuery<RemoteVideo> cq  = cb.createQuery(RemoteVideo.class);
		
		Root<RemoteVideo> video = cq.from(RemoteVideo.class);

		Predicate pred = cb.equal(video.get(RemoteVideo_.uuid),uuid);

	    cq.where(pred);
	 
	    if( 	super.getEntityManager().createQuery(cq).getResultList() == null ||
	    		super.getEntityManager().createQuery(cq).getResultList().isEmpty()	){
	    	return null;
	    }else{
		    return super.getEntityManager().createQuery(cq).getResultList().get(0);
	    }
	}

} 
